# SERVER DATA PARSER
# --------------------------------------------------------------------------
# Parse and transform data.

class DataParser

    # Require models.
    countdown = require "./../assets/js/models/countdown.coffee"

    # Require app modules.
    logger = require "./logger.coffee"
    moment = require "moment"
    settings = require "./settings.coffee"


    # DATE PARSER
    # --------------------------------------------------------------------------

    # Try to convert a string to a valid date and time. Return false if no date was found,
    # or a moment object with the date if parsing is successful.
    stringToDate = (text) ->
        if not text? or text is ""
            if settings.General.debug
                logger.info "DataParser.stringToDate", "Text is empty. Abort!"
            return false

        # Check if date should be added or subtracted.
        applyLastNext = (type, range, modifier) ->
            day = days[range]

            if day?
                diff = day - date.getDay()

                if diff is 0
                    diff = 7 * modifier
                else if diff > 0 and type is "last"
                    diff -= 7
                else if diff < 0 and type is "next"
                    diff += 7

                date.setDate date.getDate() + diff

        # Process the value to get "next" and "last" date formats.
        process = (val) ->
            split = val.split " "
            type = split[0]
            range = split[1].substring 0, 3
            typeIsNumber = /\d+/.test(type)
            ago = split[2] is "ago"

            num = ((if type is "last" then -1 else 1)) * ((if ago then -1 else 1))

            if typeIsNumber
                num *= parseInt(type, 10)

            if ranges.hasOwnProperty range
                return date["set" + ranges[range]](date["get" + ranges[range]]() + num)
            else if range is "wee"
                return date.setDate(date.getDate() + (num * 7))

            if type is "next" or type is "last"
                applyLastNext type, range, num
            else if not typeIsNumber
                return false

            return true

        # Trim and prepare the text.
        text = text.trim().replace(/\s{2,}/g, " ").replace(/[\t\r\n]/g, "").toLowerCase()

        # Parse now.
        if text is "now"
            return moment()

        # Parse tomorrow.
        if text is "tomorrow"
            return moment().add "d", 1

        # Parse yesterday.
        if text is "yesterday"
            return moment().subtract "d", 1

        # Find matched text.
        match = text.match(/^(\d{2,4})-(\d{2})-(\d{2})(?:\s(\d{1,2}):(\d{2})(?::\d{2})?)?(?:\.(\d+)?)?$/)

        if match
            year = (if match[1] >= 0 and match[1] <= 69 then +match[1] + 2000 else match[1])
            date = new Date(year, parseInt(match[2], 10) - 1, match[3], match[4] or 0, match[5] or 0, match[6] or 0, match[7] or 0)
            return moment date

        date = new Date()

        # Define specific dates.
        days =
            sun: 0
            mon: 1
            tue: 2
            wed: 3
            thu: 4
            fri: 5
            sat: 6

        # Define specific ranges.
        ranges =
            yea: "FullYear"
            mon: "Month"
            day: "Date"
            hou: "Hours"
            min: "Minutes"
            sec: "Seconds"

        regex = "([+-]?\\d+\\s" + "(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?" + "|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday" + "|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)|(last|next)\\s" + "(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?" + "|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday" + "|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))(\\sago)?"
        match = text.match(new RegExp(regex, "gi"))

        # Return false if no matched value was found.
        return false unless match

        i = 0
        len = match.length

        while i < len
            return false unless process(match[i])
            i++

        # Return a moment object with the parsed date.
        return moment date


    # HTML PARSER
    # --------------------------------------------------------------------------

    # Replace all links on text with proper A HREF tags.
    htmlLinkify: (inputText) =>
        return "" if not inputText? or inputText is ""
        result = inputText

        # URLs starting with http://, https:// or ftp://
        pattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim
        result = result.replace pattern, "<a href=\"$1\">$1</a>"

        # URLs starting with "www."
        pattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim
        result = result.replace pattern, "$1<a href=\"http://$2\">$2</a>"

        return result


    # COUNTDOWN PARSER
    # --------------------------------------------------------------------------

    # Parse a text and generate a [countdown](countdown.html) model out of it.
    # This is used mainly to transform inbound emails and twitter messages to countdown.
    # Will return null in case the text couldn't be properly parsed, or if the
    # supplied data is invalid.
    textToCountdown: (text) =>
        if not text? or text is ""
            if settings.General.debug
                logger.info "DataParser.textToCountdown", "Text is empty. Abort!", cd?.id
            return

        now = moment()
        futureDate = false
        foundDate = false
        foundDontPlaySound = false
        onEndMessage = ""

        # Split text in lines.
        lines = text.split "\n"

        # Parse each line of the text.
        for line in lines
            if not foundDate
                date = stringToDate line

                # If a valid date is found, set the `targetDate`, otherwise
                # append text to the `onEndMessage` property.
                if date isnt false
                    foundDate = true
                    futureDate = date.isAfter now

                else
                    lc = line.toLowerCase()

                    # Check if line represent a `playSound` setting.
                    if not foundDontPlaySound and lc.indexOf("playsound") >= 0 or lc.indexOf("play sound") >= 0
                        if lc.indexOf("not") >= 0 or lc.indexOf("don't") >= 0
                            foundDontPlaySound = true
                    # Nothing special found, so append the text to the `onEndMessage`.
                    else
                        onEndMessage += line + " "

        # Create new model, but only if all necessary values were found.
        if foundDate and futureDate
            cd = new countdown()
            cd.set "targetDate", date
            cd.set "onEndMessage", onEndMessage
            cd.set "playSound", not foundDontPlaySound
            return cd
        else
            return null


# Singleton implementation
# --------------------------------------------------------------------------
DataParser.getInstance = ->
    @instance = new DataParser() if not @instance?
    return @instance

module.exports = exports = DataParser.getInstance()